/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.zookeeper.server;

import java.nio.ByteBuffer;
import java.util.List;

import org.apache.jute.Record;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs.OpCode;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.txn.TxnHeader;

/**
 * This is the structure that represents a request moving through a chain of
 * RequestProcessors. There are various pieces of information that is tacked
 * onto the request as it is processed.
 */
public class Request {
	private static final Logger LOG = LoggerFactory.getLogger(Request.class);

	public final static Request requestOfDeath = new Request(null, 0, 0, 0, null, null);

	static boolean isQuorum(int type) {
		switch (type) {
		case OpCode.exists:
		case OpCode.getACL:
		case OpCode.getChildren:
		case OpCode.getChildren2:
		case OpCode.getData:
			return false;
		case OpCode.error:
		case OpCode.closeSession:
		case OpCode.create:
		case OpCode.createSession:
		case OpCode.delete:
		case OpCode.setACL:
		case OpCode.setData:
		case OpCode.check:
		case OpCode.multi:
			return true;
		default:
			return false;
		}
	}

	/**
	 * is the packet type a valid packet in zookeeper
	 * 
	 * @param type the type of the packet
	 * @return true if a valid packet, false if not
	 */
	static boolean isValid(int type) {
		// make sure this is always synchronized with Zoodefs!!
		switch (type) {
		case OpCode.notification:
			return false;
		case OpCode.create:
		case OpCode.delete:
		case OpCode.createSession:
		case OpCode.exists:
		case OpCode.getData:
		case OpCode.check:
		case OpCode.multi:
		case OpCode.setData:
		case OpCode.sync:
		case OpCode.getACL:
		case OpCode.setACL:
		case OpCode.getChildren:
		case OpCode.getChildren2:
		case OpCode.ping:
		case OpCode.closeSession:
		case OpCode.setWatches:
			return true;
		default:
			return false;
		}
	}

	static String op2String(int op) {
		switch (op) {
		case OpCode.notification:
			return "notification";
		case OpCode.create:
			return "create";
		case OpCode.setWatches:
			return "setWatches";
		case OpCode.delete:
			return "delete";
		case OpCode.exists:
			return "exists";
		case OpCode.getData:
			return "getData";
		case OpCode.check:
			return "check";
		case OpCode.multi:
			return "multi";
		case OpCode.setData:
			return "setData";
		case OpCode.sync:
			return "sync:";
		case OpCode.getACL:
			return "getACL";
		case OpCode.setACL:
			return "setACL";
		case OpCode.getChildren:
			return "getChildren";
		case OpCode.getChildren2:
			return "getChildren2";
		case OpCode.ping:
			return "ping";
		case OpCode.createSession:
			return "createSession";
		case OpCode.closeSession:
			return "closeSession";
		case OpCode.error:
			return "error";
		default:
			return "unknown " + op;
		}
	}

	public final List<Id> authInfo;

	public final ServerCnxn cnxn;

	public final long createTime = System.currentTimeMillis();

	public final int cxid;

	private KeeperException e;

	public TxnHeader hdr;

	private Object owner;

	public final ByteBuffer request;

	public final long sessionId;

	public Record txn;

	public final int type;

	public long zxid = -1;

	/**
	 * @param cnxn
	 * @param sessionId
	 * @param xid
	 * @param type
	 * @param bb
	 */
	public Request(ServerCnxn cnxn, long sessionId, int xid, int type, ByteBuffer bb, List<Id> authInfo) {
		this.cnxn = cnxn;
		this.sessionId = sessionId;
		this.cxid = xid;
		this.type = type;
		this.request = bb;
		this.authInfo = authInfo;
	}

	public KeeperException getException() {
		return e;
	}

	public Object getOwner() {
		return owner;
	}

	public void setException(KeeperException e) {
		this.e = e;
	}

	public void setOwner(Object owner) {
		this.owner = owner;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("sessionid:0x").append(Long.toHexString(sessionId)).append(" type:").append(op2String(type))
				.append(" cxid:0x").append(Long.toHexString(cxid)).append(" zxid:0x")
				.append(Long.toHexString(hdr == null ? -2 : hdr.getZxid())).append(" txntype:")
				.append(hdr == null ? "unknown" : "" + hdr.getType());

		// best effort to print the path assoc with this request
		String path = "n/a";
		if (type != OpCode.createSession && type != OpCode.setWatches && type != OpCode.closeSession && request != null
				&& request.remaining() >= 4) {
			try {
				// make sure we don't mess with request itself
				ByteBuffer rbuf = request.asReadOnlyBuffer();
				rbuf.clear();
				int pathLen = rbuf.getInt();
				// sanity check
				if (pathLen >= 0 && pathLen < 4096 && rbuf.remaining() >= pathLen) {
					byte b[] = new byte[pathLen];
					rbuf.get(b);
					path = new String(b);
				}
			} catch (Exception e) {
				// ignore - can't find the path, will output "n/a" instead
			}
		}
		sb.append(" reqpath:").append(path);

		return sb.toString();
	}
}
